
package com.fl.util;

import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;

import com.fl.util.compress.BZip2Compressor;
import com.fl.util.compress.Compressors;
import com.fl.util.compress.FileCompressor;
import com.fl.util.compress.GzipCompressor;
import com.fl.util.compress.JarCompressor;
import com.fl.util.compress.TarCompressor;
import com.fl.util.compress.ZipCompressor;

/**
 * 文件工具类<br>
 * <li>IO流复制</li> <br>
 * <li>从输入流中读取字节到字节数组缓存</li> <br>
 * <li>文件压缩和解压</li> <br>
 * <li>文件、目录删除</li> <br>
 * <li>文件、目录创建</li> <br>
 * 
 */
public class FileUtil {

    // ------------------------ File Stream Operation
    // -------------------------//
    /**
     * 复制输入、输出流<br>
     * 
     * @param in
     *            用于读取的输入流
     * @param out
     *            用于写入的输出流
     * @param buffSize
     *            缓冲大小
     * @param close
     *            是否在复制结束后在finally中关闭输入、输出流
     * 
     * @throws IOException
     */
    public static void copyBytes(InputStream in, OutputStream out, int buffSize, boolean close) throws IOException {

        try {
            copyBytes(in, out, buffSize);
        } finally {
            if (close) {
                out.close();
                in.close();
            }
        }
    }

    /**
     * 复制输入、输出流<br>
     * 
     * @param in
     *            用于读取的输入流
     * @param out
     *            用于写入的输出流
     * @param buffSize
     *            缓冲大小
     * 
     * @throws IOException
     */
    public static void copyBytes(InputStream in, OutputStream out, int buffSize) throws IOException {

        PrintStream ps = out instanceof PrintStream ? (PrintStream) out : null;
        byte buf[] = new byte[buffSize];
        int bytesRead = in.read(buf);
        while (bytesRead >= 0) {
            out.write(buf, 0, bytesRead);
            if ((ps != null) && ps.checkError()) {
                throw new IOException("Unable to write to output stream.");
            }
            bytesRead = in.read(buf);
        }
    }

    /**
     * 从输入流中复制指定字节数到输出流中<br>
     * 
     * @param in
     *            用于读取的输入流
     * @param out
     *            用于写入的输出流
     * @param length
     *            复制的字节数
     * @param bufferSize
     *            缓冲大小
     * @param close
     *            是否在复制结束后在finally中关闭输入、输出流
     * @throws IOException
     */
    public static void copyBytes(InputStream in, OutputStream out, final long length, final int bufferSize,
            final boolean close) throws IOException {
        final byte buf[] = new byte[bufferSize];
        try {
            int n = 0;
            for (long remaining = length; remaining > 0 && n != -1; remaining -= n) {
                final int toRead = remaining < buf.length ? (int) remaining : buf.length;
                n = in.read(buf, 0, toRead);
                if (n > 0) {
                    out.write(buf, 0, n);
                }
            }

            if (close) {
                out.close();
                out = null;

                in.close();
                in = null;
            }
        } finally {
            if (close) {
                closeStreams(out);
                closeStreams(in);
            }
        }
    }

    /**
     * 从输入流中读取数据到byte[]中<br>
     * 满足下面条件之一就返回<br>
     * 
     * <pre>
     * 1. 从输入流中读到EOF(-1)
     * 2. buffer字节数组读满
     * </pre>
     * 
     * @param input
     *            输入流
     * @param buffer
     *            要读到的字节数组buffer
     * @return 实际读取的长度，如读到EOF，可能比buffer的长度小
     * @throws IOException
     *             IO异常
     * @throws NullPointerException
     *             buffer==null或者input==null;
     */
    public static int read(InputStream input, byte[] buffer) throws IOException {
        int length = buffer.length;
        int remaining = length;
        while (remaining > 0) {
            int location = length - remaining;
            int count = input.read(buffer, location, remaining);
            if (-1 == count) { // EOF
                break;
            }
            remaining -= count;
        }
        return length - remaining;
    }

    /**
     * 关闭多个Closeable对象 并 吃掉 {@link IOException} 和空指针，仅用于内部异常处理.
     * 
     * @param closeables
     *            需要关闭的对象
     */
    private static void closeStreams(Closeable... closeables) {
        for (Closeable c : closeables) {
            if (c != null) {
                try {
                    c.close();
                } catch (IOException e) {
                    // 忽略所有IO异常
                    // if (log != null && log.isDebugEnabled()) {
                    // log.debug("Exception in closing " + c, e);
                    // }
                }
            }
        }
    }

    // ------------------------- File Compress & Decompress
    // ------------------------- //

    /**
     * 获取默认文件压缩器<br>
     * 默认为ZIP文件压缩器<br>
     * 
     * <pre>
     * Compressors.ZIP   ---> com.unionpay.common.util.compress.ZipCompressor
     * </pre>
     * 
     * @see com.unionpay.common.util.compress.Compressors#getCompressor(com.unionpay.common.util.compress.Compressors)
     * @return 默认文件类型的文件压缩器实例
     * */
    public static FileCompressor getCompressor() {
        return getCompressor(Compressors.ZIP);
    }

    /**
     * 获取指定文件压缩器<br>
     * 
     * <pre>
     * Compressors.ZIP   ---> com.unionpay.common.util.compress.ZipCompressor
     * Compressors.TAR   ---> com.unionpay.common.util.compress.TarCompressor
     * Compressors.JAR   ---> com.unionpay.common.util.compress.JarCompressor
     * Compressors.GZIP  ---> com.unionpay.common.util.compress.GzipCompressor
     * Compressors.BZIP2 ---> com.unionpay.common.util.compress.BZip2Compressor
     * </pre>
     * 
     * @param type
     *            文件压缩器类型
     * @see com.unionpay.common.util.compress.Compressors
     * @return 指定文件类型的文件压缩器实例
     * */
    public static FileCompressor getCompressor(Compressors type) {
        FileCompressor compressor;
        switch (type) {
            case ZIP:
                compressor = new ZipCompressor();
                break;
            case JAR:
                compressor = new JarCompressor();
                break;
            case TAR:
                compressor = new TarCompressor();
                break;
            case GZIP:
                compressor = new GzipCompressor();
                break;
            case BZIP2:
                compressor = new BZip2Compressor();
                break;
            default:
                compressor = new ZipCompressor();
                break;

        }

        return compressor;
    }

    // ------------------------------ File\Directory Create & Delete
    // ---------------------------//

    /**
     * 创建新文件或者修改已存在文件的最后更新时间<br>
     * 
     * @param file
     *            需要touch的文件
     * @throws IOException
     *             无法创建文件、文件只读、其他IO异常
     * 
     */
    public static void touch(File file) throws IOException {
        if (!file.exists()) {
            File parent = file.getParentFile();
            if (parent != null) {
                if (!parent.mkdirs() && !parent.isDirectory()) {
                    throw new IOException("Directory '" + parent + "' could not be created");
                }
            }

            OutputStream out = new FileOutputStream(file, false);
            FileUtil.closeStreams(out);
        }

        boolean success = file.setLastModified(System.currentTimeMillis());
        if (!success) {
            throw new IOException("Unable to set the last modification time for " + file);
        }
    }

    /**
     * 目录创建<br>
     * 
     * <pre>
     * 路径存在对象，但是不是目录对象，抛出IO异常
     * 尝试创建目录，创建失败，抛出IO异常
     * </pre>
     * 
     * @param directory
     *            待创建的目录，不能为 {@code null}
     * @throws NullPointerException
     *             directory对象为 {@code null}
     * @throws IOException
     *             IO异常或者路径上已经存在同名的文件
     */
    public static void forceMkdir(File directory) throws IOException {
        if (directory.exists()) {
            if (!directory.isDirectory()) {
                String message = "File " + directory + " exists and is "
                        + "not a directory. Unable to create directory.";
                throw new IOException(message);
            }
        } else {
            if (!directory.mkdirs()) {
                if (!directory.isDirectory()) {
                    String message = "Unable to create directory " + directory;
                    throw new IOException(message);
                }
            }
        }
    }

    /**
     * 删除文件或者目录<br>
     * 
     * 
     * @param file
     *            文件对象，不能为 {@code null},否则抛出NullPointerException
     * @throws NullPointerException
     *             文件对象为 {@code null}
     * @throws FileNotFoundException
     *             文件对象不存在
     * @throws IOException
     *             删除不成功
     */
    private static void forceDelete(File file) throws IOException {
        if (file.isDirectory()) {
            deleteDirectory(file);
        } else {
            boolean filePresent = file.exists();
            if (!file.delete()) {
                if (!filePresent) {
                    throw new FileNotFoundException("File does not exist: " + file);
                }
                String message = "Unable to delete file: " + file;
                throw new IOException(message);
            }
        }
    }

    /**
     * 删除一个或多个文件或者目录<br>
     * 
     * <pre>
     * 1. 目录可以不为空
     * 2. 除目录会先将递归清空，然后删除目录本身
     * 3. 删除不成功则通过异常抛出，而不是返回值
     * </pre>
     * 
     * @param files
     *            一个或多个文件（目录）对象,不能为 {@code null},否则抛出{@code NullPointerException}
     * @throws IOException
     *             IO异常
     * @see com.unionpay.common.util.FileUtil#forceDelete(file)
     * */
    public static void delete(File... files) throws IOException {
        for (File eachFile : files) {
            FileUtil.forceDelete(eachFile);
        }
    }

    /**
     * 递归删除目录<br>
     * 
     * @param directory
     *            待删除的目录
     * @throws IOException
     *             删除不成功时抛出
     */
    private static void deleteDirectory(File directory) throws IOException {
        if (!directory.exists()) {
            return;
        }

        if (!isSymlink(directory)) {
            cleanDirectory(directory);
        }

        if (!directory.delete()) {
            String message = "Unable to delete directory " + directory + ".";
            throw new IOException(message);
        }
    }

    /**
     * 清空一个目录下的所有内容，不删除目录本身<br>
     * 
     * @param directory
     *            待清空的目录
     * @throws IOException
     * 
     */
    private static void cleanDirectory(File directory) throws IOException {
        if (!directory.exists()) {
            String message = directory + " does not exist";
            throw new IllegalArgumentException(message);
        }

        if (!directory.isDirectory()) {
            String message = directory + " is not a directory";
            throw new IllegalArgumentException(message);
        }

        File[] files = directory.listFiles();
        if (files == null) { // null if security restricted
            throw new IOException("Failed to list contents of " + directory);
        }

        IOException exception = null;
        for (File file : files) {
            try {
                forceDelete(file);
            } catch (IOException ioe) {
                exception = ioe;
            }
        }

        if (null != exception) {
            throw exception;
        }
    }

    /**
     * 判断文件是一个软连接还是一个实际的文件<br>
     * 
     * @param file
     *            文件对象
     * @return true windows系统直接返回false，其他系统是软连接则返回true<br>
     * @throws IOException
     *             IO异常
     * @throws NullPointerException
     *             文件对象为空
     */
    private static boolean isSymlink(File file) throws IOException {
        if (file == null) {
            throw new NullPointerException("File must not be null");
        }
        // 根据当前系统文件路径分隔符判断系统是否windows，如果是，直接返回false
        if (File.separatorChar == '\\') {
            return false;
        }
        File fileInCanonicalDir = null;
        if (file.getParent() == null) {
            fileInCanonicalDir = file;
        } else {
            File canonicalDir = file.getParentFile().getCanonicalFile();
            fileInCanonicalDir = new File(canonicalDir, file.getName());
        }

        if (fileInCanonicalDir.getCanonicalFile().equals(fileInCanonicalDir.getAbsoluteFile())) {
            return false;
        } else {
            return true;
        }
    }

    

}
